/*
    线程池相关头文件
    实现线程池的两个关键组件：任务队列、线程池

    思想和关键注意点：
        线程池的工作原理可以简述为：管理一个任务队列，一个线程队列，
                                  然后每次去一个任务分配给一个线程去做，循环往复。
        但是从语法角度来说这是不可实现的：C++的thread都是执行一个固定的task函数，
                                   执行完之后线程也就结束了。
    
    因此CPP实现线程池的关键点如下:

    !!!创建线程后，让线程去执行调度函数，循环获取任务然后执行。
*/

#ifndef CPPTHREADPOOL
#define CPPTHREADPOOL

#include <queue>
#include <mutex>
#include <string>
#include <future>
#include <functional>
#include <thread>
#include <vector>
#include <condition_variable> 
#include <memory>
#include "spdlog/spdlog.h"
#include "spdlog/sinks/rotating_file_sink.h"


// 定义任务队列 需要考虑线程并发的问题 需要加锁
template<typename type>
class poolQueue {
private:
    std::queue<type> taskQueue;
    std::mutex qMutex;   

public:
    poolQueue();
    ~poolQueue();
    void add(type& task);
    bool empty();
    int size();
    bool getTask(type& task);
};
template<typename type>
poolQueue<type>::poolQueue() {
    spdlog::info("启用线程池！初始化线程池任务队列！");
}


template<typename type>
poolQueue<type>::~poolQueue() {
    spdlog::info("线程池任务队列析构！");
}

template<typename type>
void poolQueue<type>::add(type& task) {
    // 考虑并发问题 添加任务需要加锁
    std::unique_lock<std::mutex> lock(this->qMutex);
    this->taskQueue.emplace(task);
}

template<typename type>
bool poolQueue<type>::empty() {
    std::unique_lock<std::mutex> lock(this->qMutex);
    return this->taskQueue.empty();
}

template<typename type>
int poolQueue<type>::size() {
    std::unique_lock<std::mutex> lock(this->qMutex);
    return this->taskQueue.size();
}

template<typename type>
bool poolQueue<type>::getTask(type& task) {
    std::unique_lock<std::mutex> lock(this->qMutex);

    if (this->taskQueue.empty()) {
        //任务队列为空直接返回
        return false;
    } 

    // 右值引用获取任务
    task = std::move(this->taskQueue.front());
    this->taskQueue.pop();

    return true;
}




// 线程池部分
// 定义工作线程类
class threadPool;

class workThread {
private:
    int workId; // 任务id
    threadPool* pool; // 归属线程池

public:
    workThread(threadPool* p, int id);
    ~workThread();
    workThread(const workThread& work) = delete;
    workThread(workThread&& work);
    workThread& operator=(workThread& work) = delete;
    workThread& operator=(workThread&& work);
    // 重载操作符实现调度
    void operator()();
};

// 线程池类 
class threadPool {
private:
    // 内部封装一个工作线程类 线程不断的调度任务
    bool poolIsWork;
    poolQueue<std::function<void()>> taskQueue; // 任务队列
    std::vector<std::thread> threadVec; // 工作线程
    std::mutex poolMutex;
    std::condition_variable conditionLock; // 环境条件锁 搭配mutex使用

public:
    threadPool(unsigned int threadNum);
    ~threadPool();
    threadPool(const threadPool& pool) = delete;
    threadPool(threadPool&& pool) = delete;
    threadPool& operator=(threadPool& pool) = delete;
    threadPool& operator=(threadPool&& pool) = delete;
    bool getPoolIsWork();
    bool getTaskFromQueue(std::function<void()>& func);
    void shutDown();

    // 定义任务提交函数 线程池的重点
    template<typename F, typename... Args>
    auto addTask(F&& f, Args&&... args)->std::future<decltype(std::forward<F>(f)(std::forward<Args>(args)...))>;
};

// 线程池添加任务函数 支持任意类型任务
/*  
    函数说明：
    future部分
    {
        future是一个用来获取异步任务的结果，其存在的意义就是为了解决 std::thread 无法返回值的问题
        future可以想象成一个对未来的预言，定义它的时候，相当于某个预言家预言在未来的某一天会出现这个事件。

        预言只有一个，但预言家们倒是有很多个（即获得future的方式）:
            1.std::async 函数会返回一个std::future
            2.std::promise::get_future 调用成员函数，获取 std::future
            3.std::packaged_task::get_future 调用成员函数，获取 std::future
    }
    std::function 和 std::bind
    {
        参考链接：https://paul.pub/cpp-lambda-function-bind/
        而std::bind可以将调用函数时的部分参数先制定好，留下一部分在真正调用时确定。
        std::bind函数可以配合function函数使用，例如某些特殊情况，function对象只接受一个int类型，但是这时候有需求是
        直对范围在20到40之间的数据做处理，整体该函数结构没有必要，可以使用bind
        std::bind(isBetween, placeholders::_1, 20, 40); //placeholders占位符
    }

    std::forward
    {
        在C++中,无论以什么形式传递，函数参数都是左值，如果函数中调用另外的函数，假设是右值参数，那么就会出现错误
    }
*/
template<typename F, typename... Args>
auto threadPool::addTask(F&& f, Args&&... args)->std::future<decltype(std::forward<F>(f)(std::forward<Args>(args)...))> {
    // 创建函数对象，通过bind创建固定参数函数实体，可以直接调用
    std::function<decltype(f(args...))()> func = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
    
    // 来绑定回调函数
    /*
        std::packaged_task<decltype(f(args...))()> task(func);
        问题：为什么还需要封装一层智能指针
        答：function被包装的对象需要是一个可复制的可调用对象。
        而package_task是禁止复制操作的，仅支持移动操作，所以需要一个再次包装。
    */
    
    auto taskPtr = std::make_shared<std::packaged_task<decltype(std::forward<F>(f)(std::forward<Args>(args)...))()>>(func);
    
    std::function<void()> queueTask = [taskPtr] {
        (*taskPtr)();
    };

    this->taskQueue.add(queueTask);
    // 唤醒一个阻塞的线程 
    this->conditionLock.notify_one();

    //返回异步结果future
    return taskPtr->get_future();
}

#endif 